package com.xxlie.auth.security.endpoint;


import com.xxlie.auth.domain.User;
import com.xxlie.auth.security.WebSecurityUtils;
import com.xxlie.auth.security.auth.JwtSigner;
import com.xxlie.auth.security.auth.jwt.extractor.TokenExtractor;
import com.xxlie.auth.security.auth.jwt.verifier.TokenVerifier;
import com.xxlie.auth.security.config.JwtSettings;
import com.xxlie.auth.security.exceptions.InvalidJwtTokenException;
import com.xxlie.auth.security.model.SecurityToken;
import com.xxlie.auth.security.model.UserContext;
import com.xxlie.auth.security.model.token.JwtToken;
import com.xxlie.auth.security.model.token.JwtTokenFactory;
import com.xxlie.auth.security.model.token.RawAccessJwtToken;
import com.xxlie.auth.security.model.token.RefreshToken;
import com.xxlie.auth.service.UserService;
import com.xxlie.core.model.View;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * TokenEndpoint
 *
 * @author xxlie.coms
 * @since 2017/7/30
 */
@RestController
public class TokenEndpoint {
    @Autowired
    private JwtTokenFactory tokenFactory;
    @Autowired
    private JwtSettings jwtSettings;
    @Autowired
    private UserService userService;
    @Autowired
    private TokenVerifier tokenVerifier;
    @Autowired
    @Qualifier("jwtHeaderTokenExtractor")
    private TokenExtractor tokenExtractor;

    @Autowired
    private JwtSettings settings;

    @RequestMapping(value = "/api/auth/token", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE})
    public @ResponseBody
    View<SecurityToken> refreshToken(HttpServletRequest request, HttpServletResponse response) {
        String tokenPayload = tokenExtractor.extract(request.getHeader(WebSecurityUtils.AUTHENTICATION));

        RawAccessJwtToken rawToken = new RawAccessJwtToken(tokenPayload);
        RefreshToken refreshToken = RefreshToken.create(rawToken, jwtSettings.getTokenSigningKey()).orElseThrow(() -> new InvalidJwtTokenException("登录令牌无效"));

        String jti = refreshToken.getJti();
        if (!tokenVerifier.verify(jti)) {
            throw new InvalidJwtTokenException("登录令牌无效");
        }

        String subject = refreshToken.getSubject();
        User user = userService.getByUsername(subject).orElseThrow(() -> new UsernameNotFoundException("User not found: " + subject));

        if (user.getRole() == null)
            throw new InsufficientAuthenticationException("User has no position assigned");

        List<GrantedAuthority> authorities = new ArrayList<>(1);
        authorities.add(new SimpleGrantedAuthority(user.getRole().authority()));

        UserContext userContext = UserContext.create(user.getUsername(), authorities);

        String secret = JwtSigner.generateSecret();
        Map<String, Object> params = new HashMap<>();
        params.put(WebSecurityUtils.X_ALGORITHM, WebSecurityUtils.HMAC_SHA_512);
        params.put(WebSecurityUtils.X_SECRET, secret);

        JwtToken accessTokenNew = tokenFactory.createAccessJwtToken(userContext, params);
        JwtToken refreshTokenNew = tokenFactory.createRefreshToken(userContext, params);

        SecurityToken securityToken = new SecurityToken(accessTokenNew.getToken(), refreshTokenNew.getToken(),
                WebSecurityUtils.HMAC_SHA_512, secret, settings.getTokenExpirationTime());

        //增加用户的头像、用户名、昵称
        securityToken.setUsername(user.getUsername());
        securityToken.setNickName(user.getNickName());
        securityToken.setAvatar(user.getAvatar());

        View<SecurityToken> tokenView = View.ofOk();
        tokenView.setData(securityToken);
        return tokenView;
    }

    @RequestMapping(value = "/api/auth/logout", method = RequestMethod.GET)
    public View<String> logoutPage(HttpServletRequest request, HttpServletResponse response) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null) {
            new SecurityContextLogoutHandler().logout(request, response, auth);
        }
        return new View<>(20000, "OK");
    }
}
